﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Jayden.Dll.Core.Enums;
using Jayden.Dll.Cards.Poker.Enums;
using System.Threading;

namespace Jayden.Dll.Cards.Poker
{
	public class PokerPlayer
	{
		private string m_Login;
		private string m_Email;
		private string m_FirstName;
		private string m_LastName;
		private DateTime m_BirthDate;
		private Gender m_Gender;
		private ulong m_BankRoll;
		private ulong m_InPlay;
		private string m_Password;
		private PokerCurrency m_Currency = PokerCurrency.EUR;

		private HashSet<PokerTableSeat> m_Seats = new HashSet<PokerTableSeat>();
		private PokerSocket m_Socket;

		private bool m_AutoMuckLosingHand = false;
		private bool m_AutoWaitForBigBlind = false;

		private static Dictionary<string, PokerPlayer> m_Players = new Dictionary<string, PokerPlayer>();
		public static PokerPlayer GetPlayer(string login, string password, ulong bankRoll, PokerSocket socket)
		{
			PokerPlayer player = null;
			lock(m_Players)
			{
				if(!m_Players.TryGetValue(login, out player))
				{
					player = new PokerPlayer(login, password, bankRoll, socket);
					m_Players.Add(login, player);
				}
				else if (password == player.Password)
				{
					player.BankRoll = bankRoll;
					if (socket != null)
						player.Socket = socket;
				}
			}
			return player;
		}

		private PokerPlayer(string login, string password, ulong bankRoll, PokerSocket socket)
		{
			m_Login = login;
			m_Password = password;
			m_BankRoll = bankRoll;
			m_Socket = socket;
		}

		public string Login { get { return m_Login; } }
		public string Email { get { return m_Email; } set { m_Email = value; } }
		public string FirstName { get { return m_FirstName; } }
		public string LastName { get { return m_LastName; } }
		public DateTime BirthDate { get { return m_BirthDate; } }
		public Gender Gender { get { return m_Gender; } }
		public ulong BankRoll { get { return m_BankRoll; } set { m_BankRoll = value; } }
		public string Password { get { return m_Password; } set { m_Password = value; } }
		public PokerCurrency Currency { get { return m_Currency; } set { m_Currency = value; } }

		public PokerSocket Socket { get { return m_Socket; } set { m_Socket = value; } }

		public override bool Equals(object obj)
		{
			if(obj == this)
				return true;
			if (obj is PokerPlayer)
			{
				PokerPlayer other = obj as PokerPlayer;
				return other.Login == Login;
			}
			return false;
		}
		public override int GetHashCode() { return Login.GetHashCode(); }
		public override string ToString() { return Login; }

		public void SendCloseCards(PokerTable table, CardList cards)
		{
			if (Socket == null || !Socket.Connected)
				return;
			try
			{
				lock (Socket)
				{
					Socket.WriteInt32((int)PokerProtocolMessageType.ClosedCards);
					Socket.WriteString(table.TableName);
					Socket.WriteByte((byte)cards.Count);
					for (int c = 0; c < cards.Count; c++)
						Socket.WriteCard(cards[c]);
				}
			}
			catch
			{
			}
		}

		private Dictionary<string, ulong> m_Actions = new Dictionary<string, ulong>();
		private Dictionary<string, int> m_ActionsKey = new Dictionary<string, int>();

		public ulong GetAmount(PokerTable table, ulong callAmount, ulong minRaiseAmount, ulong allinAmount, int timeOut)
		{
			try
			{
				ulong amount;
				if (!m_Actions.TryGetValue(table.TableName, out amount))
				{
					PokerTableSeat seat = table[Login];
					if (Socket == null)
						return 0;
					if (!Socket.Connected)
						return 0;
					Random random = new Random();
					int actionKey = random.Next();
					while (actionKey == 0)
						actionKey = random.Next();
					m_ActionsKey.Add(table.TableName, actionKey);
					lock (Socket)
					{
						Socket.WriteInt32((int)PokerProtocolMessageType.Action);
						Socket.WriteString(table.TableName);
						Socket.WriteInt32(actionKey);
						Socket.WriteInt32(timeOut);
						Socket.WriteUInt64(callAmount);
						Socket.WriteUInt64(minRaiseAmount);
						Socket.WriteUInt64(allinAmount);
					}
					lock (seat)
					{
						if(!m_Actions.ContainsKey(table.TableName))
						{
							if (!Monitor.Wait(seat, timeOut))
								return 0;
						}
						amount = m_Actions[table.TableName];
					}
				}
				m_Actions.Remove(table.TableName);
				return amount;
			}
			finally
			{
				m_ActionsKey.Remove(table.TableName);
			}
		}
		public bool GetForcedPost(PokerTable table, bool smallAndBig, int timeOut)
		{
			if (m_AutoWaitForBigBlind)
				return false;
			try
			{
				ulong amount;
				if (!m_Actions.TryGetValue(table.TableName, out amount))
				{
					PokerTableSeat seat = table[Login];
					if (Socket == null)
						throw new NotImplementedException();
					if (!Socket.Connected)
						return false;
					Random random = new Random();
					int actionKey = random.Next();
					while (actionKey == 0)
						actionKey = random.Next();
					m_ActionsKey.Add(table.TableName, actionKey);
					lock (Socket)
					{
						Socket.WriteInt32((int)PokerProtocolMessageType.ActionPost);
						Socket.WriteString(table.TableName);
						Socket.WriteInt32(actionKey);
						Socket.WriteInt32(timeOut);
						Socket.WriteBoolean(smallAndBig);
					}
					lock (seat)
					{
						if (!m_Actions.ContainsKey(table.TableName))
						{
							if (!Monitor.Wait(seat, timeOut))
								return false;
						}
						amount = m_Actions[table.TableName];
					}
				}
				m_Actions.Remove(table.TableName);
				return amount != 0;
			}
			finally
			{
				m_ActionsKey.Remove(table.TableName);
			}
		}
		public bool GetShowCards(PokerTable table, int timeOut)
		{
			if (m_AutoMuckLosingHand)
				return false;
			try
			{
				ulong amount;
				if (!m_Actions.TryGetValue(table.TableName, out amount))
				{
					PokerTableSeat seat = table[Login];
					if (Socket == null)
						throw new NotImplementedException();
					if (!Socket.Connected)
						return false;
					Random random = new Random();
					int actionKey = random.Next();
					while (actionKey == 0)
						actionKey = random.Next();
					m_ActionsKey.Add(table.TableName, actionKey);
					lock (Socket)
					{
						Socket.WriteInt32((int)PokerProtocolMessageType.ActionShow);
						Socket.WriteString(table.TableName);
						Socket.WriteInt32(actionKey);
						Socket.WriteInt32(timeOut);
					}
					lock (seat)
					{
						if (!m_Actions.ContainsKey(table.TableName))
						{
							if (!Monitor.Wait(seat, timeOut))
								return false;
						}
						amount = m_Actions[table.TableName];
					}
				}
				m_Actions.Remove(table.TableName);
				return amount != 0;
			}
			finally
			{
				m_ActionsKey.Remove(table.TableName);
			}
		}
		public bool GetRebuy(PokerTable table, int timeOut, out byte rebuyCount)
		{
			try
			{
				rebuyCount = 0;
				ulong amount;
				if (!m_Actions.TryGetValue(table.TableName, out amount))
				{
					PokerTableSeat seat = table[Login];
					if (Socket == null)
						throw new NotImplementedException();
					if (!Socket.Connected)
						return false;
					PokerTournamentConfiguration config = table.Configuration as PokerTournamentConfiguration;
					if (config == null)
						return false;
					Random random = new Random();
					int actionKey = random.Next();
					while (actionKey == 0)
						actionKey = random.Next();
					m_ActionsKey.Add(table.TableName, actionKey);
					lock (Socket)
					{
						Socket.WriteInt32((int)PokerProtocolMessageType.TournamentRebuy);
						Socket.WriteString(config.TournamentName);
						Socket.WriteInt32(actionKey);
						Socket.WriteInt32(timeOut);
					}
					lock (seat)
					{
						if (!m_Actions.ContainsKey(table.TableName))
						{
							if (!Monitor.Wait(seat, timeOut))
								return false;
						}
						amount = m_Actions[table.TableName];
					}
				}
				m_Actions.Remove(table.TableName);
				rebuyCount = (byte)amount;
				return amount != 0;
			}
			finally
			{
				m_ActionsKey.Remove(table.TableName);
			}
		}

		public void SetAmount(PokerTable table, ulong amount, int actionKey)
		{
			PokerTableSeat seat = table[Login];
			if (seat != null)
			{
				int key = 0;
				if (actionKey == 0 || (m_ActionsKey.TryGetValue(table.TableName, out key) && key == actionKey))
				{
					m_ActionsKey.Remove(table.TableName);
					m_Actions.Remove(table.TableName);
					m_Actions.Add(table.TableName, amount);
					lock (seat)
					{
						Monitor.PulseAll(seat);
					}
				}
			}
		}
		public void SetShowHide(PokerTable table, bool show, int actionKey)
		{
			PokerTableSeat seat = table[Login];
			if (seat != null)
			{
				int key = 0;
				if (m_ActionsKey.TryGetValue(table.TableName, out key) && key == actionKey)
				{
					m_ActionsKey.Remove(table.TableName);
					m_Actions.Remove(table.TableName);
					m_Actions.Add(table.TableName, show ? 1ul : 0ul);
					lock (seat)
					{
						Monitor.PulseAll(seat);
					}
				}
			}
		}
		public void SetPost(PokerTable table, bool post, int actionKey)
		{
			PokerTableSeat seat = table[Login];
			if (seat != null)
			{
				int key = 0;
				if (m_ActionsKey.TryGetValue(table.TableName, out key) && key == actionKey)
				{
					m_ActionsKey.Remove(table.TableName);
					m_Actions.Remove(table.TableName);
					m_Actions.Add(table.TableName, post ? 1ul : 0ul);
					lock (seat)
					{
						Monitor.PulseAll(seat);
					}
				}
			}
		}
		public void SetRebuy(PokerTable table, byte rebuyCount, int actionKey)
		{
			PokerTableSeat seat = table[Login];
			if (seat != null)
			{
				int key = 0;
				if (m_ActionsKey.TryGetValue(table.TableName, out key) && key == actionKey)
				{
					m_ActionsKey.Remove(table.TableName);
					m_Actions.Remove(table.TableName);
					m_Actions.Add(table.TableName, rebuyCount);
					lock (seat)
					{
						Monitor.PulseAll(seat);
					}
				}
			}
		}

		public List<PokerTableSeat> Seats
		{
			get
			{
				lock (m_Seats)
				{
					return new List<PokerTableSeat>(m_Seats);
				}
			}
		}

		public void AddSeat(PokerTableSeat seat)
		{
			lock (m_Seats)
			{
				m_Seats.Add(seat);
			}
		}
		public void RemoveSeat(PokerTableSeat seat)
		{
			lock (m_Seats)
			{
				m_Seats.Remove(seat);
			}
		}

		public void SendTournamentResult(PokerTournamentConfiguration tournament, int position, PokerTournamentConfiguration.PayoutEntry payout)
		{
			if (m_Socket == null)
				return;
			if (!m_Socket.Connected)
				return;
			lock(m_Socket)
			{
				m_Socket.WriteInt32((int)PokerProtocolMessageType.TournamentFinished);
				m_Socket.WriteString(tournament.TournamentName);
				m_Socket.WriteInt32(position);
				if(payout != null)
					m_Socket.WriteUInt64(payout.Amount);
				else
					m_Socket.WriteUInt64(0);
				m_Socket.WriteString(tournament.EntryCurrency.IsoCode);
			}
		}

		public PokerTable GetTableByTournamentName(string tournamentName)
		{
			lock (m_Seats)
			{
				foreach (PokerTableSeat seat in m_Seats)
				{
					PokerTournamentConfiguration config = seat.Table.Configuration as PokerTournamentConfiguration;
					if (config != null)
					{
						if (config.TournamentName == tournamentName)
							return seat.Table;
					}
				}
			}
			return null;
		}
	}
}
